General-Purpose Modules are Deeper
general-purpose moduleとspecial-purpose module
直訳で汎用モジュールと特殊なモジュール?rkasu.icon
具体的なモジュールの実装例
エディタを作成する要件
ファイルを表示できる
ユーザーがポイント、クリック、タイプしてファイルを編集できる
同じファイルの複数の同時ビューを異なるウィンドウでサポートする
ファイルへの変更に対する複数レベルのUndoとRedoをサポートする
考えられる操作
バックスペースキーでカーソル左の文字を削除
デリートキーでカーソル右の文字を削除
テキストの選択範囲のコピーと削除
テキストの挿入
検索と置換(汎用APIの応用例として言及)
最初に作ったもの
UIの操作(バックスペース、デリート、選択削除…)がそのままTextクラスのメソッドになっていて浅いモジュールになっている
https://scrapbox.io/files/69cca9eb9ea0767ac8600db7.svg
エディタを作る例
多くの学生チームは、テキストクラスに対してspecial-purpose fashionの実装を行っていた
例)ユーザーがバックスペースキーを押すと、エディタはカーソルの左の文字を削除し、デリートキーを押すと、カーソルの右の文字を削除みたいなケース
code:java
void backspace(Cursor cursor);
void delete(Cursor cursor);
void deleteSelection(Selection selection)
こういうメソッドをクラスに追加していた
3つとも別々のメソッドとして存在していたことでUIの特定の操作と1対1で対応する専用メソッドになっている
つまりテキストクラスにメソッドがどんどん追加される構造になっている
これはテキストの特定範囲を削除するという一つの汎用的な操作として使うことで表現できるはず
code:java
void delete(Position start, Position end);
こんな感じにしておけばUI固有の概念を一切知る必要がなくテキストクラスを変更する必要もない
フロントエンドで似たようなよくあるケース
トーストの表示がUIごとに変わるとかそうかもrkasu.icon
本業で昔あった
code:typescript
const useNotification = () => ({
showSuccessBanner: (msg: string) => { ... },
showErrorDialog: (msg: string) => { ... },
showWarningToast: (msg: string) => { ... },
showInfoSnackbar: (msg: string) => { ... },
});
こういうのだとバナーとか、ダイアログとかトーストのようなUIごとに関数が生えていた
これを整理してメッセージとタイプ(重要度のようなもの)を返す関数にするとUIに依存しない
code:typescript
type Level = "success" | "error" | "warning" | "info";
const useNotification = () => ({
notify: (message: string, level: Level, options?: NotifyOptions) => { ... },
});
更に踏み込むと汎用的なAPIにすると、情報漏洩の解消だけでなく、コードの明白さ・コード量の削減・再利用性などが改善される
次に改良したもの
多数の浅いメソッドが insert と delete の二つに統合。
バックスペースやデリートの「意味」はUI側が組み立てる
Textクラスは純粋にテキスト操作だけを担う
UI操作が増えてもTextクラスの変更は不要
https://scrapbox.io/files/69cca9fe9ea0767ac8600def.svg
とはいえ特殊化されたモジュールを完全に排除するのは不可能
ただし特殊化されたモジュールは汎用モジュールから分離されるべきものである
その方法として上方、下方に押し込んであげる
上に押しやる
例えばデータフェッチ関数の中で受け取ったものをフィルタリングして返すような関数
汎用的なフェッチ関数に切り出す
汎用的なフェッチ関数を使いつつその後固有の処理であるフィルタリング処理を行う関数としてページトップなどで処理する上へのお仕上げ
下に押しやる
あんまりいい例が思いつかないがコンポーネントライブラリを直接使わずにラップするような感じだと浅いモジュールになってしまうrkasu.icon
さらに改良したもの
上(ポリシー)どこまでを一括Undo/Redoにするか、UI固有のアクション(選択・カーソル)はUI側が定義
中央(仕組み)HistoryクラスはActionのリストを管理するだけ。中身が何であるかの関心はない
下方(詳細)テキストの挿入・削除に関するUndo/Redoの具体的な処理は、テキスト操作と密接に関わるのでTextクラス内に配置
https://scrapbox.io/files/69ccaa249ea0767ac8600e74.svg
ソフトウェア設計の最も重要な要素の一つは、誰が何をいつ知る必要があるかを決定すること!
常にモジュールを作るときに自分に問うことも大事
現在のすべてのニーズをカバーする最もシンプルなインターフェースは何か?
このメソッドはいくつの状況で使われるか?
このAPIは現在のニーズに対して使いやすいか?